package de.recipeminer.persistence;

import com.db4o.Db4oEmbedded;
import com.db4o.ObjectContainer;
import com.db4o.ObjectSet;
import com.db4o.config.EmbeddedConfiguration;
import com.db4o.ext.DatabaseClosedException;
import com.db4o.ext.DatabaseReadOnlyException;
import com.db4o.ext.Db4oIOException;
import com.db4o.ext.ExtObjectContainer;
import com.db4o.query.Predicate;
import com.db4o.query.Query;
import com.db4o.query.QueryComparator;
import org.apache.log4j.Logger;

import java.io.File;
import java.util.Comparator;

/**
 * A decorating adapter class for {@link ObjectContainer}, providing the same operation as the adapted
 * class, extended by additional convenience methods.
 *
 * @author Markus Ackermann
 */
public class Db4oContainer implements ObjectContainer {
    static Logger logger = Logger.getLogger(Db4oContainer.class);
    private ObjectContainer container;
    private File dataBaseFile;
    private Db4oConfigurationProvider configurationProvider;

    public Db4oContainer(Db4oConfigurationProvider configProvider, File dbFile) {
        dataBaseFile = dbFile;
        configurationProvider = configProvider;
    }

    /**
     * Deletes all Objects stored in this database.
     * Handle with care!
     *
     * @return number of deleted objects
     */
    public long clear() {
        ObjectSet<Object> result = query(new Predicate<Object>() {
            @Override
            public boolean match(Object o) {
                return true;
            }
        });
        logger.info(String.format("Clearing %d objects.", result.size()));
        while (result.hasNext()) {
            delete(result.next());
        }
        commit();
        return result.size();
    }

    ObjectSet<Object> getAll() {
        return container.query().execute();
    }

    /**
     * If this container does not already mantain an open connection to the associated
     * database file, open a connection to it.
     * This leads to a lock on this database file until
     * {@link Db4oContainer#close()}  or {@link Db4oContainer#closeAllSessions()} is used.
     */
    void open() {
        if (container == null || container.ext().isClosed()) {
            logger.info("Opening database file: " + getDatabaseFile().getAbsolutePath());
            EmbeddedConfiguration config = configurationProvider.getConfig();
            container = Db4oEmbedded.openFile(config, dataBaseFile.getPath());
        }
    }

    /**
     * Closes all {@link ObjectContainer}s repesenting basic connections or session to the
     * associated databse file. This ensures that the lock on the database file is released,
     * but may invalidate database session not mantained by the instance acutally receiving the
     * method call.
     */
    public void closeAllSessions() {
        open();
        if (!container.ext().isClosed()) {
            logger.debug("Closing database file: " + getDatabaseFile().getAbsolutePath());
            int sessions = -1;
            while (!container.close())
                sessions++;
            if (sessions > 0)
                logger.warn(String.format("Closed %d remaining sessions.", sessions));
        }
    }


    /**
     * For testing purpose, to see if objects really are persisted,
     * not just lingering in the Object cache of a db4o session.
     */
    public void reopen() {
        closeAllSessions();
        open();
    }

    /**
     * Return {@link File} path abstraction for the associated databse file.
     *
     * @return {@link File} used as database file for {@code this}
     */
    public File getDatabaseFile() {
        return dataBaseFile;
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public void activate(Object obj, int depth) throws Db4oIOException, DatabaseClosedException {
        open();
        container.activate(obj, depth);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean close() throws Db4oIOException {
        return container != null ? container.close() : true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void commit() throws Db4oIOException, DatabaseClosedException, DatabaseReadOnlyException {
        open();
        container.commit();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void deactivate(Object obj, int depth) throws DatabaseClosedException {
        open();
        container.deactivate(obj, depth);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void delete(Object obj) throws Db4oIOException, DatabaseClosedException, DatabaseReadOnlyException {
        open();
        container.delete(obj);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ExtObjectContainer ext() {
        open();
        return container.ext();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <T> ObjectSet<T> queryByExample(Object template) throws Db4oIOException, DatabaseClosedException {
        open();
        return container.queryByExample(template);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Query query() throws DatabaseClosedException {
        open();
        return container.query();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <TargetType> ObjectSet<TargetType> query(Class<TargetType> class_)
            throws Db4oIOException, DatabaseClosedException {
        open();
        return container.query(class_);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <TargetType> ObjectSet<TargetType> query(Predicate<TargetType> predicate)
            throws Db4oIOException, DatabaseClosedException {
        open();
        return container.query(predicate);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <TargetType> ObjectSet<TargetType> query(Predicate<TargetType> predicate,
                                                    QueryComparator<TargetType> comparator)
            throws Db4oIOException, DatabaseClosedException {
        open();
        return container.query(predicate, comparator);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <TargetType> ObjectSet<TargetType> query(Predicate<TargetType> predicate, Comparator<TargetType> comparator)
            throws Db4oIOException, DatabaseClosedException {
        open();
        return container.query(predicate, comparator);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void rollback() throws Db4oIOException, DatabaseClosedException, DatabaseReadOnlyException {
        open();
        container.rollback();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void store(Object obj) throws DatabaseClosedException, DatabaseReadOnlyException {
        open();
        container.store(obj);
    }
}
